<!DOCTYPE html>
<html lang="zh-CN">





<head>
  <meta charset="UTF-8">
  <link rel="apple-touch-icon" sizes="76x76" href="/img/favicon.ico">
  <link rel="icon" type="image/png" href="/img/favorite.ico">
  <meta name="viewport"
        content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, shrink-to-fit=no">
  <meta http-equiv="x-ua-compatible" content="ie=edge">
  
  <meta name="theme-color" content="#2f4154">
  <meta name="description" content="小九的日常笔记">
  <meta name="author" content="小九">
  <meta name="keywords" content="笔记，博客，前端，html，css，javascript，面试题，前端面试题">
  <title>深拷贝浅拷贝和赋值的原理及实现剖析 - 小九的书柜</title>

  <link  rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/4.4.1/css/bootstrap.min.css" />


  <link  rel="stylesheet" href="https://cdn.staticfile.org/github-markdown-css/4.0.0/github-markdown.min.css" />
  <link  rel="stylesheet" href="/lib/hint/hint.min.css" />

  
    <link  rel="stylesheet" href="https://cdn.staticfile.org/highlight.js/10.0.0/styles/github-gist.min.css" />
  

  


<!-- 主题依赖的图标库，不要自行修改 -->

<link rel="stylesheet" href="//at.alicdn.com/t/font_1749284_yg9cfy8wd6.css">



<link rel="stylesheet" href="//at.alicdn.com/t/font_1736178_pjno9b9zyxs.css">


<link  rel="stylesheet" href="/css/main.css" />

<!-- 自定义样式保持在最底部 -->

  
<link rel="stylesheet" href="/css/my.css">



<!-- 
<link rel="stylesheet" href="/css/message.css">
 -->
<!-- 
<link rel="stylesheet" href="https://at.alicdn.com/t/font_1117508_wxidm5ry7od.css">
 -->

  <script  src="/js/utils.js" ></script>
<meta name="generator" content="Hexo 4.2.1"></head>



<body>
  
  <div id="background">
    <!--  -->
    <header style="height: 70vh;">
      <nav id="navbar" class="navbar fixed-top  navbar-expand-lg navbar-dark scrolling-navbar">
  <div class="container">
    <a class="navbar-brand"
      href="/">&nbsp;<strong>jLongYan's blog</strong>&nbsp;</a>

    <button id="navbar-toggler-btn" class="navbar-toggler" type="button" data-toggle="collapse"
      data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false"
      aria-label="Toggle navigation">
      <div class="animated-icon"><span></span><span></span><span></span></div>
    </button>

    <!-- Collapsible content -->
    <div class="collapse navbar-collapse" id="navbarSupportedContent">
      <ul class="navbar-nav ml-auto text-center">
        
        
        
        
        
        

        <li class="nav-item 1 " </li>
          <a class="nav-link 1 " href="/">
            <i class="iconfont icon-home-fill"></i>
            首页
          </a>
        </li>
        
        
        
        
        
        
        

        <li class="nav-item 1 " </li>
          <a class="nav-link 1 " href="/archives/">
            <i class="iconfont icon-archive-fill"></i>
            归档
          </a>
        </li>
        
        
        
        
        
        
        

        <li class="nav-item 1 " </li>
          <a class="nav-link 1 " href="/categories/">
            <i class="iconfont icon-category-fill"></i>
            分类
          </a>
        </li>
        
        
        
        
        
        
        

        <li class="nav-item 1 " </li>
          <a class="nav-link 1 " href="/tags/">
            <i class="iconfont icon-tags-fill"></i>
            标签
          </a>
        </li>
        
        
        
        
        
        
        

        <li class="nav-item 1 " </li>
          <a class="nav-link 1 " href="/links/">
            <i class="iconfont icon-link-fill"></i>
            友链
          </a>
        </li>
        
        
        
        
        
        
        

        <li class="nav-item 1 " </li>
          <a class="nav-link 1 " href="/about/">
            <i class="iconfont icon-user-fill"></i>
            关于
          </a>
        </li>
        
        
        
        <li class="nav-item" onclick="switchDarkMode()">
          <span id="dark"></span>
        </li>
        
      </ul>
    </div>
  </div>
</nav>
<style>
  .active {
    /* border-bottom: 1px solid #00FFFF; */
    color: #00FFFF !important;
  }
</style>

<script>
  var isNight = new Date().getHours() >= 22 || new Date().getHours() < 5; // 指定时间
  // 依次判断 系统暗黑模式 指定时间 缓存 dark
  if (matchMedia('(prefers-color-scheme: dark)').matches || isNight || localStorage.getItem('dark') === '1') {
    if (!(isNight && localStorage.getItem('noDark') === '1')) {
      document.body.classList.add('dark');
    }
  }
  //点击事件
  function switchDarkMode() {
    if ($('body').hasClass('dark')) {
      // checkWallaper()
      document.getElementById('dark').style.backgroundImage = 'url(/img/太阳.png)'
      document.body.classList.remove('dark');
      localStorage.setItem('noDark', '1');
      localStorage.setItem('dark', '0');
    } else {
      document.getElementById('dark').style.backgroundImage = 'url(/img/月亮.png)'
      document.body.classList.add('dark');
      localStorage.setItem('dark', '1');
      localStorage.setItem('noDark', '0');
      // checkWallaper()
    }
  }

</script>



      <!-- 如果需要采用配置文件的路径 在下面的div粘贴此段代码进去即可 目前采用的是 自定义的随机图片 -->
      <!-- style="background: url('/img/default.png') no-repeat center center;background-size: cover;" -->
      <div class="view intro-2" id="indexBackground" parallax=true
        style='background-size:cover'>
        <div class="full-bg-img">
          <div class="mask flex-center" style="background-color: rgba(0, 0, 0, 0.1)">
            <div class="container text-center white-text fadeInUp">

              <span class="h2" id="subtitle">
                
              </span>

              <div id="yiyan" onclick="watchYiyan()">只有极其努力，才能看起来毫不费力。</div>

              


              
              
  <div class="mt-3 post-meta">
    <i class="iconfont icon-date-fill" aria-hidden="true"></i>
    <time datetime="2018-07-19 20:18">
      2018年7月19日 晚上
    </time>
  </div>


<div class="mt-1">
  
    
    <span class="post-meta mr-2">
      <i class="iconfont icon-chart"></i>
      4.9k 字
    </span>
  

  
    
    <span class="post-meta mr-2">
      <i class="iconfont icon-clock-fill"></i>
      
      
      61
       分钟
    </span>
  

  
  
    
  
</div>
<!-- <script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script> -->
              
            </div>

            
            <div class="scroll-down-bar">
              <i class="iconfont icon-arrowdown"></i>
            </div>
            
          </div>
        </div>
      </div>
    </header>
    <!--  -->



    <main>
      
      

<div class="container-fluid">
  <div class="row">
    <div class="d-none d-lg-block col-lg-2"></div>
    <div class="col-lg-8 nopadding-md">
      <div class="container nopadding-md" id="board-ctn">
        <div class="py-5" id="board">
          <div class="post-content mx-auto" id="post">
            
              <p class="note note-info">
                
                  本文最后更新于：2020年8月9日 晚上
                
              </p>
            
            <article class="markdown-body">
              <h4 id="深拷贝浅拷贝和赋值的原理及实现剖析"><a href="#深拷贝浅拷贝和赋值的原理及实现剖析" class="headerlink" title="深拷贝浅拷贝和赋值的原理及实现剖析"></a>深拷贝浅拷贝和赋值的原理及实现剖析</h4><hr>
<p>在工作中我们经常会用到深拷贝与浅拷贝，但是你有没有去分析什么场景下使用它，为什么需要使用呢，深浅拷贝有何异同呢，什么是深拷贝呢，如何实现呢，你会有这些问题吗，今天就为大家总结一下吧。</p>
<h5 id="栈内存与堆内存"><a href="#栈内存与堆内存" class="headerlink" title="栈内存与堆内存"></a>栈内存与堆内存</h5><p>在了解这个问题之前需要先了解下栈内存与堆内存的概念，这里可以看看我的上一篇文章<a href="/posts/7b91b8a9.html">堆与栈的概念</a>。了解完了这个概念再来了解这个就非常简单了。</p>
<h5 id="区别"><a href="#区别" class="headerlink" title="区别"></a>区别</h5><ul>
<li>浅拷贝—拷贝的是一个对象的指针,而不是复制对象本身，拷贝出来的对象共用一个指针，其中一个改变了值，其他的也会同时改变。</li>
<li>深拷贝—拷贝出来一个新的对象，开辟一块新的空间，拷贝前后的对象相互独立，互相不会改变，拥有不同的指针。</li>
</ul>
<p>简单的总结下，假设有个A，我们拷贝了一个为B，就是修改A或者B的时候看看另一个会不会也变化，如果改变A的值B也变了那么就是浅拷贝，如果改变A之后B的值没有发生变化就是深拷贝，当然这是基础理解，下面我们一起来分析下吧。</p>
<h5 id="赋值"><a href="#赋值" class="headerlink" title="赋值"></a>赋值</h5><pre><code class="hljs javascript"><span class="hljs-comment">/** demo1基本数据类型 */</span>
<span class="hljs-keyword">let</span> a = <span class="hljs-number">1</span>;
<span class="hljs-keyword">let</span> b = a;
b = <span class="hljs-number">10</span>;
<span class="hljs-built_in">console</span>.log(a,b)<span class="hljs-comment">//  1    10</span>
<span class="hljs-comment">/** demo2引用数据类型 */</span>
<span class="hljs-keyword">let</span> a = &#123;
    name: <span class="hljs-string">'小九'</span>,
    age: <span class="hljs-number">23</span>,
    favorite: [<span class="hljs-string">'吃饭'</span>,<span class="hljs-string">'睡觉'</span>,<span class="hljs-string">'打豆豆'</span>]
&#125;
<span class="hljs-keyword">let</span> b = a;
a.name = <span class="hljs-string">'小七'</span>
a.age = <span class="hljs-number">18</span>
a.favorite = [<span class="hljs-string">'上班'</span>,<span class="hljs-string">'下班'</span>,<span class="hljs-string">'加班'</span>]
<span class="hljs-built_in">console</span>.log(a,b)
<span class="hljs-comment">/** </span>
<span class="hljs-comment">&#123; name: '小七', age: 18, favorite: [ '上班', '下班', '加班' ] &#125; </span>
<span class="hljs-comment">&#123; name: '小七', age: 18, favorite: [ '上班', '下班', '加班' ] &#125;</span>
<span class="hljs-comment">*/</span></code></pre>

<p>通过看上面的例子可以看出通过赋值去拿到新的值，赋值对于基本数据来说就是在栈中新开了一个变量，相当于是两个独立的栈内存，所以相互不会影响，但是对于引用数据类型，他只是复制了一份a在栈内存的指针，所以两个指针指向了同一个堆内存的空间，通过任何一个指针改变值都会影响其他的，通过这样的赋值可以产生多个指针，但是堆内存的空间始终只有一个，这就是赋值产生的问题，我们在开发中当然不希望改变B而影响了A，所以这个时候就需要用到浅拷贝和深拷贝了。</p>
<ul>
<li>针对基本数据类型，随便赋值都不会相互影响</li>
<li>针对引用数据类型，赋值就会出现我们不想看到的，改动一方双方都变化。</li>
</ul>
<h5 id="浅拷贝"><a href="#浅拷贝" class="headerlink" title="浅拷贝"></a>浅拷贝</h5><h6 id="Object-assign"><a href="#Object-assign" class="headerlink" title="Object.assign()"></a>Object.assign()</h6><pre><code class="hljs javascript"><span class="hljs-comment">/** Object.assign */</span>
<span class="hljs-keyword">let</span> A = &#123;
    name: <span class="hljs-string">'小九'</span>,
    age: <span class="hljs-number">23</span>,
    sex: <span class="hljs-string">'男'</span>
&#125;
<span class="hljs-keyword">let</span> B = <span class="hljs-built_in">Object</span>.assign( &#123;&#125;, A);
B.name = <span class="hljs-string">'小七'</span>
B.sex = <span class="hljs-string">'女'</span>
B.age = <span class="hljs-number">18</span>
<span class="hljs-built_in">console</span>.log(A,B)
<span class="hljs-comment">/** &#123; name: '小九', age: 23, sex: '男' &#125; &#123; name: '小七', age: 18, sex: '女' &#125; */</span></code></pre>



<p>首先实现浅拷贝的第一个方法是通过 <code>Object.assign()</code>这个 方法，<code>Object.assign()</code> 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。</p>
<p>先不管这个方法具体干嘛，我们先来看看结果，我们发现拷贝一个A之后的B改变了<code>name</code>、<code>age</code>、<code>sex</code>之后A的值并没有发生变化，在这里，你可能会想，这不是就成功了么，AB宜家互相不影响了，可是和我们上面讲的浅拷贝会AB互相不变化就是深拷贝产生了矛盾，那么是为什么呢，其实上面已经说到了，这个demo里面用到的全是基本数据类型，所以拷贝和赋值一样，针对基本数据类型，都是在栈重新开辟一个变量，所以相互不会影响，那我们看看引用数据类型</p>
<pre><code class="hljs javascript"><span class="hljs-keyword">let</span> A = &#123;
    name: <span class="hljs-string">'小九'</span>,
    age: <span class="hljs-number">23</span>,
    sex: <span class="hljs-string">'男'</span>,
    favorite: &#123;
        item_a:[<span class="hljs-string">'打游戏'</span>,<span class="hljs-string">'上网'</span>],
        item_b:[<span class="hljs-string">'读书'</span>,<span class="hljs-string">'网课'</span>]
    &#125;
&#125;
<span class="hljs-keyword">let</span> B = <span class="hljs-built_in">Object</span>.assign( &#123;&#125;, A);

B.name = <span class="hljs-string">'小七'</span>
B.sex = <span class="hljs-string">'女'</span>
B.age = <span class="hljs-number">18</span>
B.favorite.item_a =[<span class="hljs-string">'打篮球'</span>] 
B.favorite.item_b =[<span class="hljs-string">'写笔记'</span>] 
<span class="hljs-built_in">console</span>.log(A)
<span class="hljs-built_in">console</span>.log(B)
<span class="hljs-comment">/** 打印结果对比 */</span>
&#123; <span class="hljs-attr">name</span>: <span class="hljs-string">'小九'</span>,
  age: <span class="hljs-number">23</span>,
  sex: <span class="hljs-string">'男'</span>,
  favorite: &#123; <span class="hljs-attr">item_a</span>: [ <span class="hljs-string">'打篮球'</span> ], <span class="hljs-attr">item_b</span>: [ <span class="hljs-string">'写笔记'</span> ] &#125; &#125;
----------------------------------------------------------------
&#123; <span class="hljs-attr">name</span>: <span class="hljs-string">'小七'</span>,
  age: <span class="hljs-number">18</span>,
  sex: <span class="hljs-string">'女'</span>,
  favorite: &#123; <span class="hljs-attr">item_a</span>: [ <span class="hljs-string">'打篮球'</span> ], <span class="hljs-attr">item_b</span>: [ <span class="hljs-string">'写笔记'</span> ] &#125; &#125;</code></pre>

<p>通过对比发现我们同样拷贝了A之后发现改变B的<code>name</code> <code>age</code> <code>sex</code>都不会影响，但是改变<code>facorite</code>的时候却影响了A，那么问题来了，这我们通过浅拷贝发现依然无法满足我们的需求，改变B同样影响了A，回到这个方法，<code>Object.assign()</code>这个方法是可以把任意的多个源对象的可枚举属性拷贝给目标对象，然后返回目标对象，它进行的是<strong>对象的浅拷贝，拷贝的是对象的引用，而不是对象本身</strong>，所以针对于这种有两层的数据结构就出出现只拷贝了第一层，第二层以下的对象依然拷贝不了，所以我们称<code>Object.assign()</code>为浅拷贝，只有在对象只有一层结构的时候才时候使用，</p>
<ul>
<li>很多人说<code>Object.assign</code>是深拷贝，其实是错误的，</li>
<li>浅拷贝是按位拷贝对象，<strong>它会创建一个新对象</strong>，这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型，拷贝的就是基本类型的值；如果属性是内存地址（引用类型），拷贝的就是内存地址 ，因此如果其中一个对象改变了这个地址，就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝)，即只复制对象空间而不复制资源。</li>
<li>该方法只拷贝源对象自身的属性，不拷贝其继承的属性。</li>
<li>该方法不会拷贝对象不可枚举的属性</li>
<li>undefined和null无法转成对象，他们不能作为Object.assign参数，但是可以作为源对象</li>
<li>属性为Symbol的值，可以被该方法拷贝。</li>
<li>浅拷贝，拷贝了第一层的基本数据类型结构，但是深层的依然没有拷贝到，也就是第一层基本类型数据已经不会影响了，但是引用却不行，所以还不够</li>
</ul>
<h6 id="Array-prototype-silce"><a href="#Array-prototype-silce" class="headerlink" title="Array.prototype.silce"></a>Array.prototype.silce</h6><p>看这个方法之前先给大家看看mdn对于这个方法的描述。</p>
<blockquote>
<p> <strong>返回值</strong></p>
<p>返回一个新的数组，包含从 start 到 end （不包括该元素）的 arrayObject 中的元素。</p>
<p> <strong>说明</strong> </p>
<p>请注意，该方法并不会修改数组，而是返回一个子数组。如果想删除数组中的一段元素，应该使用方法 Array.splice()。</p>
</blockquote>
<p>看完它的描述大家就应该差不多明白了吧，让我们继续用刚刚的例子来实现下</p>
<pre><code class="hljs javascript"><span class="hljs-keyword">let</span> A = [<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>,[<span class="hljs-number">4</span>,<span class="hljs-number">5</span>]]
<span class="hljs-keyword">let</span> B = A.slice();
B[<span class="hljs-number">3</span>] = <span class="hljs-number">4</span>
<span class="hljs-built_in">console</span>.log(A)
<span class="hljs-built_in">console</span>.log(B)
<span class="hljs-comment">/** 对比 */</span>
[ <span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, [ <span class="hljs-number">4</span>, <span class="hljs-number">5</span> ] ]
[ <span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span> ]</code></pre>

<p>可以发现互相不会影响，也就实现了浅拷贝，同理针对于复杂的多层数据结构和之前一样也会互相影响，所以个人理解，这个浅字也是由此而来吧，所以上面的说法也不是很准备，不一定AB互相不影响就一定是深拷贝了，还得结合数据结构层级来看。</p>
<h6 id="Array-from"><a href="#Array-from" class="headerlink" title="Array.from()"></a>Array.from()</h6><p>先来看一句mdn的描述</p>
<blockquote>
<p> <strong>Array.from()</strong>  方法从一个类似数组或可迭代对象创建一个新的，浅拷贝的数组实例。</p>
</blockquote>
<p><code>Array.form()</code> 用于将两类对象转成真正的数组，一种是like-array(类数组),和可遍历的(iterable)对象，我们可以利用这个方法来进行一个浅拷贝。</p>
<pre><code class="hljs javascript"><span class="hljs-keyword">let</span> A = [<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>,[<span class="hljs-number">4</span>,<span class="hljs-number">5</span>]] ;
<span class="hljs-keyword">let</span> B = <span class="hljs-built_in">Array</span>.from(A) ;
B[<span class="hljs-number">3</span>] = <span class="hljs-number">6</span>
<span class="hljs-built_in">console</span>.log(A)
<span class="hljs-built_in">console</span>.log(B)
<span class="hljs-comment">/** 对比结果 */</span>
[ <span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, [ <span class="hljs-number">4</span>, <span class="hljs-number">5</span> ] ]
[ <span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">6</span> ]</code></pre>

<p>可以发现，也是一样的效果，可以实现。</p>
<h6 id="Array-prototype-concat"><a href="#Array-prototype-concat" class="headerlink" title="Array.prototype.concat"></a>Array.prototype.concat</h6><pre><code class="hljs javascript"><span class="hljs-keyword">let</span> A = [<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>,[<span class="hljs-number">4</span>,<span class="hljs-number">5</span>]] ;
<span class="hljs-keyword">let</span> B = [].concat(A) ;
B[<span class="hljs-number">3</span>] = <span class="hljs-number">6</span>
<span class="hljs-built_in">console</span>.log(A)
<span class="hljs-built_in">console</span>.log(B)
<span class="hljs-comment">/** 对比结果 */</span>
[ <span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, [ <span class="hljs-number">4</span>, <span class="hljs-number">5</span> ] ]
[ <span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">6</span> ]</code></pre>

<p>数组的方法原理大同小异，适当了解就行，可以自行操作试试看。</p>
<h6 id="ES6-gt"><a href="#ES6-gt" class="headerlink" title="ES6 -&gt; []"></a>ES6 -&gt; []</h6><p>ES6的扩展运算符也可以轻松做到，也非常方便来看看吧</p>
<pre><code class="hljs javascript"><span class="hljs-keyword">let</span> A = [<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>,[<span class="hljs-number">4</span>,<span class="hljs-number">5</span>]]
<span class="hljs-keyword">let</span> B =[...A]
B[<span class="hljs-number">3</span>] = <span class="hljs-number">6</span>
<span class="hljs-built_in">console</span>.log(A)
<span class="hljs-built_in">console</span>.log(B)
<span class="hljs-comment">/** 对比结果 */</span>
[ <span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, [ <span class="hljs-number">4</span>, <span class="hljs-number">5</span> ] ]
[ <span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">6</span> ]</code></pre>

<p>扩展运算符是es6新增的特性，作用很强大也非常方便，也是我日常爱用的一种方式，对象，数组都可以操作，除了轻松实现浅拷贝，合并对象也非常的轻松，可以多多使用。</p>
<h6 id="for-in"><a href="#for-in" class="headerlink" title="for in"></a>for in</h6><p>先写个简单版本，因为这个也可以实现深拷贝，所以直接动手吧，</p>
<pre><code class="hljs javascript"><span class="hljs-keyword">let</span> A = [<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>,[<span class="hljs-number">4</span>,<span class="hljs-number">5</span>]]
<span class="hljs-keyword">let</span> B = []
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> i <span class="hljs-keyword">in</span> A)&#123;
    B[i] = A[i]
&#125;

B[<span class="hljs-number">3</span>] = <span class="hljs-number">9</span>
<span class="hljs-built_in">console</span>.log(A,B)
<span class="hljs-comment">/** 对比结果 */</span>
[ <span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, [ <span class="hljs-number">4</span>, <span class="hljs-number">5</span> ] ]
[ <span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">9</span> ]</code></pre>

<p>发现同样可以实现，原理也很简单，自行分析下。</p>
<p>浅拷贝的实现有很多种方法，不单单是我这里写出的六种，当然，实际开发中，我们更注重的是深拷贝，所以我们来看看如何实现一个深拷贝吧。</p>
<h5 id="深拷贝"><a href="#深拷贝" class="headerlink" title="深拷贝"></a>深拷贝</h5><h6 id="JSON-parse-JSon-stringify"><a href="#JSON-parse-JSon-stringify" class="headerlink" title="JSON.parse(JSon.stringify())"></a>JSON.parse(JSon.stringify())</h6><pre><code class="hljs javascript"><span class="hljs-comment">/** 乞丐版本  JSON.parse(JSON.stringify()) */</span>
<span class="hljs-keyword">let</span> A = &#123;
    a: <span class="hljs-number">1</span>,
    b: <span class="hljs-number">2</span>,
    c: [<span class="hljs-number">4</span>,<span class="hljs-number">5</span>,<span class="hljs-number">6</span>]
&#125;
<span class="hljs-keyword">let</span> B = <span class="hljs-built_in">JSON</span>.parse(<span class="hljs-built_in">JSON</span>.stringify(A))
B.a = <span class="hljs-number">2</span> 
B.b = <span class="hljs-number">3</span>
B.c = <span class="hljs-number">4</span>
<span class="hljs-built_in">console</span>.log(A == B)
<span class="hljs-built_in">console</span>.log(A,B)
<span class="hljs-comment">/** 对比结果 </span>
<span class="hljs-comment"> * &#123; a: 1, b: 2, c: [ 4, 5, 6 ] &#125; </span>
<span class="hljs-comment"> * &#123; a: 2, b: 3, c: 4 &#125;</span>
<span class="hljs-comment"> */</span></code></pre>

<p>可以发现，使用这个方法可以做到拷贝之后的AB互相不受影响，成为单独一个新值，我们来分析下，这个方法里面我们用到了两个东西，分别是<code>JSON.stringify()</code>、<code>JSON.parse()</code>这两个方法，首先通过stringify将json序列化(json字符串)，然后在通过parse实现反序列(还原)js对象，序列化的作用是存储和传输，在这个过程中就会开启新的内存空间就会产生和源对象不一样的空间从而实现深拷贝，实际开发中这个用法已经可以解决很多场景了，但是依然有很多弊端。</p>
<ul>
<li>如果obj里面有时间对象，则JSON.stringify后再JSON.parse的结果，时间将只是字符串的形式。而不是时间对象；</li>
<li>如果obj里有RegExp、Error对象，则序列化的结果将只得到空对象；</li>
<li>如果obj里有函数，undefined，则序列化的结果会把函数或 undefined丢失；</li>
<li>如果obj里有NaN、Infinity和-Infinity，则序列化的结果会变成null</li>
<li>JSON.stringify()只能序列化对象的可枚举的自有属性，例如 如果obj中的对象是有构造函数生成的，  使用这个方法后会丢弃对象的constructor。</li>
<li>该方法不能拷贝function类型</li>
</ul>
<p>综上所看，这个方法也有不少的问题，当然对于一个合格的程序员来说，这个版本也过于low，我们当然也希望实现的更加全面一点。</p>
<h6 id="基础版本-浅拷贝"><a href="#基础版本-浅拷贝" class="headerlink" title="基础版本(浅拷贝)"></a>基础版本(浅拷贝)</h6><pre><code class="hljs javascript"><span class="hljs-comment">/** 基础版本 for  in */</span>

<span class="hljs-keyword">let</span> A = &#123;
    a: [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>],
    b: &#123; <span class="hljs-attr">a</span>: <span class="hljs-number">1</span>,<span class="hljs-attr">b</span>: <span class="hljs-number">2</span>&#125;,
    c: <span class="hljs-number">99</span>
&#125;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">deepClone</span>(<span class="hljs-params">target</span>) </span>&#123;
    <span class="hljs-keyword">let</span> return_result = &#123;&#125;
    <span class="hljs-keyword">for</span>(<span class="hljs-keyword">let</span> key <span class="hljs-keyword">in</span> target) &#123;
        return_result[key] = target[key]
    &#125;
    <span class="hljs-keyword">return</span> return_result
&#125;

<span class="hljs-keyword">let</span> B = deepClone(A)
B.a= <span class="hljs-number">99</span>
B.b = <span class="hljs-number">88</span>
<span class="hljs-built_in">console</span>.log(A,<span class="hljs-string">'----------'</span>,B)
<span class="hljs-comment">/** 对比结果 </span>
<span class="hljs-comment"> *  &#123; a: [ 1, 2, 3 ], b: &#123; a: 1, b: 2 &#125;, c: 99 &#125;</span>
<span class="hljs-comment"> *  &#123; a: 99, b: 88, c: 99 &#125;</span>
<span class="hljs-comment"> */</span></code></pre>



<p>可以看到，通过for in可以实现一个基础的浅拷贝，和<code>Object.assign()</code>一样，只能拷贝第一层，但是我们初步已经成功了，</p>
<p>接下来我们需要考虑的是需要考虑数组了吧，上面只能是对象，也很简单，我们只需要加个判断就行，接下来改造一下：</p>
<h6 id="兼容数组-浅拷贝"><a href="#兼容数组-浅拷贝" class="headerlink" title="兼容数组(浅拷贝)"></a>兼容数组(浅拷贝)</h6><pre><code class="hljs javascript"><span class="hljs-comment">/** 基础版本 for  in + 兼容数组 */</span>
<span class="hljs-keyword">let</span> A = [<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>,[<span class="hljs-number">4</span>,<span class="hljs-number">5</span>]]
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">deepClone</span>(<span class="hljs-params">target</span>) </span>&#123;
    <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> target == <span class="hljs-string">'object'</span>)&#123; <span class="hljs-comment">//先判断是不是引用数据类型</span>
        <span class="hljs-keyword">let</span> return_result =  <span class="hljs-built_in">Array</span>.isArray(target) ? [] : &#123;&#125;
        <span class="hljs-keyword">for</span>(<span class="hljs-keyword">let</span> key <span class="hljs-keyword">in</span> target) &#123;
            return_result[key] = target[key]
        &#125;
        <span class="hljs-keyword">return</span> return_result
    &#125;<span class="hljs-keyword">else</span>&#123;
        <span class="hljs-keyword">return</span> target;
    &#125;
&#125;
<span class="hljs-keyword">let</span> B = deepClone(A)
B[<span class="hljs-number">3</span>]= <span class="hljs-number">99</span>
B[<span class="hljs-number">2</span>] = <span class="hljs-number">88</span>
<span class="hljs-built_in">console</span>.log(A,<span class="hljs-string">'----------'</span>,B)
<span class="hljs-comment">/** 对比结果</span>
<span class="hljs-comment"> *  [ 1, 2, 3, [ 4, 5 ] ]</span>
<span class="hljs-comment"> *  [ 1, 2, 88, 99 ]</span>
<span class="hljs-comment"> */</span></code></pre>

<p>可以看到，现在已经可以兼容数组了，但是依然不够，我们依然只能拷贝第一层，所以接下来需要对深层侧的对象进行递归拷贝了，继续刚刚的方法改进吧：</p>
<h6 id="基础版本-兼容数组-递归调用-深拷贝"><a href="#基础版本-兼容数组-递归调用-深拷贝" class="headerlink" title="基础版本+兼容数组+递归调用(深拷贝)"></a>基础版本+兼容数组+递归调用(深拷贝)</h6><pre><code class="hljs javascript"><span class="hljs-comment">/** 兼容数组 + 递归调用 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">deepClone</span>(<span class="hljs-params">target</span>) </span>&#123;
    <span class="hljs-keyword">let</span> result;
    <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> target === <span class="hljs-string">'object'</span>) &#123;
        <span class="hljs-keyword">if</span> (<span class="hljs-built_in">Array</span>.isArray(target)) &#123;
            result = []
            <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i <span class="hljs-keyword">in</span> target) &#123;
                result.push(deepClone(target[i]))
            &#125;
        &#125; <span class="hljs-keyword">else</span> &#123;
            result = &#123;&#125;
            <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> key <span class="hljs-keyword">in</span> target) &#123;
                result[key] = target[key]
            &#125;
        &#125;
    &#125; <span class="hljs-keyword">else</span> &#123;
        result = target;
    &#125;
    <span class="hljs-keyword">return</span> result;
&#125;
<span class="hljs-keyword">let</span> A = [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, &#123; <span class="hljs-attr">a</span>: <span class="hljs-number">1</span>, <span class="hljs-attr">b</span>: <span class="hljs-number">2</span> &#125;]
<span class="hljs-keyword">let</span> B = deepClone(A)
B[<span class="hljs-number">3</span>].a = <span class="hljs-number">99</span>
<span class="hljs-built_in">console</span>.log(A)
<span class="hljs-built_in">console</span>.log(B)
<span class="hljs-comment">/** 对比结果</span>
<span class="hljs-comment"> *  [ 1, 2, 3, [ 4, 5 ] ]</span>
<span class="hljs-comment"> *  [ 1, 2, 3, &#123; a: 99, b: 2 &#125; ]</span>
<span class="hljs-comment"> */</span></code></pre>

<p>我们先判断其类型，再对对象和数组分别递归调用，如果是基本数据类型就直接赋值，至此，我们已经可以完成一个基础的深拷贝，但是还远远不够，因为我们这里只对数组做了类型判断，其他默认都是object，但是实际情况还会有很多类型，例如，RegExp，Date，Null，Undefined，function等等很多的类型，所以接下来我们将其完善，加上所以判断，由于类型比较多，我们可以把对象的判断单独抽离出来，接下来一起完善它吧：在这之前我们还需要考虑的一个点就是 关于js的<code>循环引用问题</code>当目前的这个方法去拷贝一个带有循环引用关系的对象时是有问题的，来看看：</p>
<pre><code class="hljs javascript"> <span class="hljs-comment">/** 基础版本 for  in + 兼容数组 + 递归调用 + 循环引用问题 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">deepClone</span>(<span class="hljs-params">target</span>) </span>&#123;
    <span class="hljs-keyword">let</span> result;
    <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> target === <span class="hljs-string">'object'</span>) &#123;
        <span class="hljs-keyword">if</span> (<span class="hljs-built_in">Array</span>.isArray(target)) &#123;
            result = []
            <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i <span class="hljs-keyword">in</span> target) &#123;
                result.push(deepClone(target[i]))
            &#125;
        &#125; <span class="hljs-keyword">else</span> &#123;
            result = &#123;&#125;
            <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> key <span class="hljs-keyword">in</span> target) &#123;
                result[key] = target[key]
            &#125;
        &#125;
    &#125; <span class="hljs-keyword">else</span> &#123;
        result = target;
    &#125;
    <span class="hljs-keyword">return</span> result;
&#125;
<span class="hljs-keyword">let</span> A = [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, &#123; <span class="hljs-attr">a</span>: <span class="hljs-number">1</span>, <span class="hljs-attr">b</span>: <span class="hljs-number">2</span> &#125;]
A[<span class="hljs-number">4</span>] = A
<span class="hljs-keyword">let</span> B = deepClone(A)
<span class="hljs-built_in">console</span>.log(A,B)
<span class="hljs-comment">/**  RangeError: Maximum call stack size exceeded */</span></code></pre>

<p>会出现一个超出了最大调用堆栈大小的错误，这也是深拷贝中的一个坑，在这里我们可以通过js的一种weakmap的类型来解决这个问题，通过阅读mdn的文档可以了解到：</p>
<blockquote>
<p>原生的 WeakMap 持有的是每个键对象的“弱引用”，这意味着在没有其他引用存在时垃圾回收能正确进行。原生 WeakMap 的结构是特殊且有效的，其用于映射的 key 只有在其没有被回收时才是有效的。</p>
<p>正由于这样的弱引用，<code>WeakMap</code> 的 key 是不可枚举的 (没有方法能给出所有的 key)。如果key 是可枚举的话，其列表将会受垃圾回收机制的影响，从而得到不确定的结果。因此，如果你想要这种类型对象的 key 值的列表，你应该使用 <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Map" target="_blank" rel="noopener"><code>Map</code></a>。</p>
<p>基本上，如果你要往对象上添加数据，又不想干扰垃圾回收机制，就可以使用 WeakMap。</p>
</blockquote>
<p>解决：使用一个<code>WeakMap</code>结构存储已经被拷贝的对象，每一次进行拷贝的时候就先向<code>WeakMap</code>查询该对象是否已经被拷贝，如果已经被拷贝则取出该对象并返回。</p>
<pre><code class="hljs javascript"><span class="hljs-comment">/** 基础版本 for  in + 兼容数组 + 递归调用 + 解决循环引用问题 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">deepClone</span>(<span class="hljs-params">val, hash = new WeakMap(</span>)) </span>&#123;
    <span class="hljs-keyword">if</span> (hash.has(val)) <span class="hljs-keyword">return</span> hash.get()
    <span class="hljs-keyword">let</span> cloneVal;
    <span class="hljs-keyword">if</span> (isObj(val)) &#123; <span class="hljs-comment">//判断是不是引用类型</span>
        <span class="hljs-keyword">if</span> (<span class="hljs-built_in">Array</span>.isArray(val)) &#123; <span class="hljs-comment">// 判断是不是数组 </span>
            cloneVal = []
            hash.set(val, cloneVal)
            <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i <span class="hljs-keyword">in</span> val) &#123;
                cloneVal.push(deepClone(val[i]))
            &#125;
        &#125;
        <span class="hljs-keyword">else</span> &#123;
            cloneVal = &#123;&#125;
            hash.set(val, cloneVal)
            <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> key <span class="hljs-keyword">in</span> val) &#123;
                cloneVal[key] = val[key]
            &#125;
        &#125;
    &#125; <span class="hljs-keyword">else</span> &#123;
        cloneVal = val;
    &#125;
    <span class="hljs-keyword">return</span> cloneVal;
&#125;
<span class="hljs-comment">/** 是否是引用类型 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">isObj</span>(<span class="hljs-params">val</span>) </span>&#123;
    <span class="hljs-keyword">return</span> (<span class="hljs-keyword">typeof</span> val == <span class="hljs-string">'object'</span> || <span class="hljs-keyword">typeof</span> val == <span class="hljs-string">'function'</span>) &amp;&amp; val != <span class="hljs-literal">null</span>
&#125;
<span class="hljs-keyword">var</span> a = &#123;&#125;
a.a = a
<span class="hljs-keyword">var</span> b = deepClone(a)
<span class="hljs-built_in">console</span>.log(b)</code></pre>

<p>这样就可以初步解决循环调用问题，接下来要考虑的是如何为更多类型做不同处理，我们借用之前的一个检测js类型的文章，通过<a href="/posts/6c6ef731.html">js检测数据类型</a> 的这个方法来为多种类型分别处理。</p>
<pre><code class="hljs javascript"><span class="hljs-comment">/** 完整版本 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">deepClonea</span>(<span class="hljs-params">val, map = new WeakMap(</span>)) </span>&#123;
    <span class="hljs-keyword">let</span> type = getType(val); <span class="hljs-comment">//当是引用类型的时候先拿到其确定的类型</span>
    <span class="hljs-keyword">if</span> (isObj(val)) &#123;
        <span class="hljs-keyword">switch</span> (type) &#123;
            <span class="hljs-keyword">case</span> <span class="hljs-string">'date'</span>:                   <span class="hljs-comment">//日期类型重新new一次传入之前的值，date实例化本身结果不变</span>
                <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(val);
                <span class="hljs-keyword">break</span>;
            <span class="hljs-keyword">case</span> <span class="hljs-string">'regexp'</span>:                 <span class="hljs-comment">//正则类型直接new一个新的正则传入source和flags即可</span>
                <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">RegExp</span>(val.source, val.flags);
                <span class="hljs-keyword">break</span>;
            <span class="hljs-keyword">case</span> <span class="hljs-string">'function'</span>:               <span class="hljs-comment">//如果是函数类型就直接通过function包裹返回一个新的函数，并且改变this指向</span>
                <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">RegExp</span>(val.source, val.flags);
                <span class="hljs-keyword">break</span>;
            <span class="hljs-keyword">default</span>:
                <span class="hljs-keyword">let</span> cloneVal = <span class="hljs-built_in">Array</span>.isArray(val) ? [] : &#123;&#125;;
                <span class="hljs-keyword">if</span> (map.has(val)) <span class="hljs-keyword">return</span> map.get(val)
                map.set(val, cloneVal)
                <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> key <span class="hljs-keyword">in</span> val) &#123;
                    <span class="hljs-keyword">if</span> (val.hasOwnProperty(key)) &#123; <span class="hljs-comment">//判断是不是自身的key</span>
                        cloneVal[key] = deepClone(val[key]), map;<span class="hljs-comment">//每一项就算是基本类型也需要走deepclone方法进行拷贝</span>
                    &#125;
                &#125;
                <span class="hljs-keyword">return</span> cloneVal;
        &#125;
    &#125; <span class="hljs-keyword">else</span> &#123;
        <span class="hljs-keyword">return</span> val;     <span class="hljs-comment">//当是基本数据类型的时候直接返回</span>
    &#125;
&#125;
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">isObj</span>(<span class="hljs-params">val</span>) </span>&#123;   <span class="hljs-comment">//判断是否是引用类型</span>
    <span class="hljs-keyword">return</span> (<span class="hljs-keyword">typeof</span> val == <span class="hljs-string">'object'</span> || <span class="hljs-keyword">typeof</span> val == <span class="hljs-string">'function'</span>) &amp;&amp; val != <span class="hljs-literal">null</span>
&#125;
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getType</span>(<span class="hljs-params">data</span>) </span>&#123; <span class="hljs-comment">//获取类型</span>
    <span class="hljs-keyword">var</span> s = <span class="hljs-built_in">Object</span>.prototype.toString.call(data);
    <span class="hljs-keyword">return</span> s.match(<span class="hljs-regexp">/\[object (.*?)\]/</span>)[<span class="hljs-number">1</span>].toLowerCase();
&#125;;
<span class="hljs-comment">// /** 测试 */</span>
<span class="hljs-keyword">var</span> a = &#123;&#125;
a.a = a
<span class="hljs-keyword">var</span> b = deepClonea(a)
<span class="hljs-built_in">console</span>.log(b)</code></pre>

<h6 id="最终完整版"><a href="#最终完整版" class="headerlink" title="最终完整版"></a>最终完整版</h6><hr>
<p>上面差不多已经完成了一个可以应对大部分场景的深拷贝了，下面让我们用class类的方法来改造一下，方便后期对其进行扩展更改。</p>
<pre><code class="hljs javascript"><span class="hljs-comment">/**  改用class类写 */</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DeepClone</span> </span>&#123;
    <span class="hljs-keyword">constructor</span>()&#123;
        cloneVal: <span class="hljs-literal">null</span>;
    &#125;
    clone(val, map = <span class="hljs-keyword">new</span> <span class="hljs-built_in">WeakMap</span>()) &#123;
        <span class="hljs-keyword">let</span> type = <span class="hljs-keyword">this</span>.getType(val); <span class="hljs-comment">//当是引用类型的时候先拿到其确定的类型</span>
        <span class="hljs-keyword">if</span> (<span class="hljs-keyword">this</span>.isObj(val)) &#123;
            <span class="hljs-keyword">switch</span> (type) &#123;
                <span class="hljs-keyword">case</span> <span class="hljs-string">'date'</span>:             <span class="hljs-comment">//日期类型重新new一次传入之前的值，date实例化本身结果不变</span>
                    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(val);
                    <span class="hljs-keyword">break</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">'regexp'</span>:           <span class="hljs-comment">//正则类型直接new一个新的正则传入source和flags即可</span>
                    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">RegExp</span>(val.source, val.flags);
                    <span class="hljs-keyword">break</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">'function'</span>:        <span class="hljs-comment">//如果是函数类型就直接通过function包裹返回一个新的函数，并且改变this指向</span>
                    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">RegExp</span>(val.source, val.flags);
                    <span class="hljs-keyword">break</span>;
                <span class="hljs-keyword">default</span>:
                    <span class="hljs-keyword">this</span>.cloneVal = <span class="hljs-built_in">Array</span>.isArray(val) ? [] : &#123;&#125;;
                    <span class="hljs-keyword">if</span> (map.has(val)) <span class="hljs-keyword">return</span> map.get(val)
                    map.set(val, <span class="hljs-keyword">this</span>.cloneVal)
                    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> key <span class="hljs-keyword">in</span> val) &#123;
                        <span class="hljs-keyword">if</span> (val.hasOwnProperty(key)) &#123; <span class="hljs-comment">//判断是不是自身的key</span>
                            <span class="hljs-keyword">this</span>.cloneVal[key] = <span class="hljs-keyword">new</span> DeepClone().clone(val[key], map);
                        &#125;
                    &#125;
                    <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.cloneVal;
            &#125;
        &#125; <span class="hljs-keyword">else</span> &#123;
            <span class="hljs-keyword">return</span> val;     <span class="hljs-comment">//当是基本数据类型的时候直接返回</span>
        &#125;
    &#125;
    <span class="hljs-comment">/** 判断是否是引用类型 */</span>
    isObj(val) &#123;   
        <span class="hljs-keyword">return</span> (<span class="hljs-keyword">typeof</span> val == <span class="hljs-string">'object'</span> || <span class="hljs-keyword">typeof</span> val == <span class="hljs-string">'function'</span>) &amp;&amp; val != <span class="hljs-literal">null</span>
    &#125;
    <span class="hljs-comment">/** 获取类型 */</span>
    getType(data) &#123; 
        <span class="hljs-keyword">var</span> s = <span class="hljs-built_in">Object</span>.prototype.toString.call(data);
        <span class="hljs-keyword">return</span> s.match(<span class="hljs-regexp">/\[object (.*?)\]/</span>)[<span class="hljs-number">1</span>].toLowerCase();
    &#125;;
&#125;
 <span class="hljs-comment">/** 测试 */</span>
<span class="hljs-keyword">var</span> a =&#123;
    a:<span class="hljs-number">1</span>,
    b:<span class="hljs-literal">true</span>,
    c:<span class="hljs-literal">undefined</span>,
    d:<span class="hljs-literal">null</span>,
    e:<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">a,b</span>)</span>&#123;
        <span class="hljs-keyword">return</span> a + b
    &#125;,
    f: <span class="hljs-regexp">/\W+/gi</span>,
    time: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(),

&#125;
<span class="hljs-keyword">const</span> deepClone = <span class="hljs-keyword">new</span> DeepClone()
<span class="hljs-keyword">let</span> b = deepClone.clone(a)
<span class="hljs-built_in">console</span>.log(b)</code></pre>

<p>好了上面就是本次总结的深拷贝，当然还不够完善，还有很多种场景，后期可能会补充，但是这个目前已经可以应对你很大一部分的场景了。</p>

            </article>
            <hr>
            <div>
              <div class="post-metas mb-3">
                
                  <div class="post-meta mr-3">
                    <i class="iconfont icon-category"></i>
                    
                      <a class="hover-with-bg" href="/categories/javascript/">javascript</a>
                    
                  </div>
                
                
                  <div class="post-meta">
                    <i class="iconfont icon-tags"></i>
                    
                      <a class="hover-with-bg" href="/tags/js/">js</a>
                    
                      <a class="hover-with-bg" href="/tags/%E6%B7%B1%E6%8B%B7%E8%B4%9D/">深拷贝</a>
                    
                      <a class="hover-with-bg" href="/tags/%E6%B5%85%E6%8B%B7%E8%B4%9D/">浅拷贝</a>
                    
                  </div>
                
              </div>
              
                <p class="note note-warning">本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-sa/4.0/deed.zh" target="_blank" rel="nofollow noopener noopener">CC BY-SA 4.0 协议</a> ，转载请注明出处！</p>
              
              
                <div class="post-prevnext row">
                  <div class="post-prev col-6">
                    
                    
                      <a href="/posts/6c6ef731.html">
                        <i class="iconfont icon-arrowleft"></i>
                        <span class="hidden-mobile">Js检测数据类型的多种方法</span>
                        <span class="visible-mobile">上一篇</span>
                      </a>
                    
                  </div>
                  <div class="post-next col-6">
                    
                    
                      <a href="/posts/7b91b8a9.html">
                        <span class="hidden-mobile">栈内存与堆内存的区别</span>
                        <span class="visible-mobile">下一篇</span>
                        <i class="iconfont icon-arrowright"></i>
                      </a>
                    
                  </div>
                </div>
              
            </div>

            
              <!-- Comments -->
              <div class="comments" id="comments">
                
                
  <div id="vcomments"></div>
  <script type="text/javascript">
    function loadValine() {
      addScript('https://cdn.staticfile.org/valine/1.4.14/Valine.min.js', function () {
        new Valine({
          el: "#vcomments",
          app_id: "3PpvuTN5QkO4Tih7TqE1W5u8-gzGzoHsz",
          app_key: "2URikzfu2z1jtCXMp3Sy6Oin",
          placeholder: "说点什么",
          path: window.location.pathname,
          avatar: "retro",
          meta: ["nick","mail","link"],
          pageSize: "10",
          lang: "zh-CN",
          highlight: false,
          recordIP: false,
          serverURLs: "",
        });
      });
    }
    createObserver(loadValine, 'vcomments');
  </script>
  <noscript>Please enable JavaScript to view the <a href="https://valine.js.org" target="_blank" rel="nofollow noopener noopener">comments
      powered by Valine.</a></noscript>


              </div>
            
          </div>
        </div>
      </div>
    </div>
    
      <div class="d-none d-lg-block col-lg-2 toc-container" id="toc-ctn">
        <div id="toc">
  <p class="toc-header"><i class="iconfont icon-list"></i>&nbsp;目录</p>
  <div id="tocbot"></div>
</div>

      </div>
    
  </div>
</div>

<!-- Custom -->



<!-- <script>
//去掉畅言下面的跳转连接
  //  声明定时器
var timer = null
//  检查dom是否执行完成
function checkDom() {
    let dom = document.querySelector('.service-wrap-w')
    if(dom) {
      console.log(dom)
         //  执行dom加载完成后的操作
         dom.innerHTML = ''
        //  清除定时器
        if(!timer) {
            clearTimeout(timer)
        }
    } else {
        //  自我调用
        timer = setTimeout(checkDom, 0)
    }
}
//  首次执行
checkDom()
</script> -->
      
    </main>

    
    <a id="scroll-top-button" href="#" role="button">
      <i class="iconfont icon-arrowup" aria-hidden="true"></i>
    </a>
    

    
    <div class="modal fade" id="modalSearch" tabindex="-1" role="dialog" aria-labelledby="ModalLabel"
     aria-hidden="true">
  <div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
    <div class="modal-content">
      <div class="modal-header text-center">
        <h4 class="modal-title w-100 font-weight-bold">搜索</h4>
        <button type="button" id="local-search-close" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body mx-3">
        <div class="md-form mb-5">
          <input type="text" id="local-search-input" class="form-control validate">
          <label data-error="x" data-success="v"
                 for="local-search-input">关键词</label>
        </div>
        <div class="list-group" id="local-search-result"></div>
      </div>
    </div>
  </div>
</div>
    

    

    

    <footer class="mt-5">
  <div class="text-center py-3">
    <div>
      <!-- <a  rel="nofollow noopener"><span class="motto">故事若不曲折怎么教人成长</span></a> -->

      <!-- <a rel="nofollow noopener"><span class="motto">xiaojiu</span></a> -->
      <!-- <i class="iconfont icon-love"></i> -->
      <a rel="nofollow noopener">

        <!-- <span class="motto"> 可惜故事太长只有风听我讲</span></a> -->
        
        <span class="motto"> 黄粱一梦、山水万重、人间总相逢</span></a>
        <!-- <span class="motto"> blog</span></a> -->

    </div>
    


    

    
    <div class="xiaojiu">
      <span id="xiaojiu_container_site_pv" style="display: none;">本站已经被访问- <span id="xiaojiu_value_site_pv"
          style="margin: 0 3px;"> </span> -次啦</span>
      <span id="xiaojiu_container_site_uv" style="display: none;">已经有- <span id="xiaojiu_value_site_uv"
          style="margin: 0 3px;"> </span> -个小伙伴来过啦</span>
    </div>


    <div class="date">
      <span id="timeDate">载入天数...</span>
      <span class="times" id="times">载入时分秒...
      </span>
      <img class="time" src="/img/sml.gif" srcset="/img/loading.gif" alt="">
      <script>
        var now = new Date();
        function createtime() {
          var grt = new Date("05/08/2018 00:00:00");//此处修改你的建站时间或者网站上线时间
          now.setTime(now.getTime() + 250);
          days = (now - grt) / 1000 / 60 / 60 / 24;
          dnum = Math.floor(days);
          hours = (now - grt) / 1000 / 60 / 60 - (24 * dnum);
          hnum = Math.floor(hours);
          if (String(hnum).length == 1) {
            hnum = "0" + hnum;
          }
          minutes = (now - grt) / 1000 / 60 - (24 * 60 * dnum) - (60 * hnum);
          mnum = Math.floor(minutes);
          if (String(mnum).length == 1) {
            mnum = "0" + mnum;
          }
          seconds = (now - grt) / 1000 - (24 * 60 * 60 * dnum) - (60 * 60 * hnum) - (60 * mnum);
          snum = Math.round(seconds);
          if (String(snum).length == 1) {
            snum = "0" + snum;
          }
          document.getElementById("timeDate").innerHTML = "本站已运行&nbsp" + dnum + "&nbsp天";
          document.getElementById("times").innerHTML = hnum + "&nbsp小时&nbsp" + mnum + "&nbsp分&nbsp" + snum + "&nbsp秒";
        }
        setInterval("createtime()", 250);
      </script>
    </div>
</footer>
<!-- <script src="https://api.lzwzw.cn/public/js/xiaojiu.js"></script> -->
<!-- <script src="/js/xiaojiu.js"></script> -->
<script src="/js/message.js"></script>

<style>
  .xiaojiu {
    margin-top: 14px !important;
    background-image: linear-gradient(135deg, deeppink, dodgerblue);
    font-weight: bold;
    -webkit-background-clip: text;
    -moz-background-clip: text;
    background-clip: text;
    box-decoration-break: clone;
    -webkit-box-decoration-break: clone;
    -moz-box-decoration-break: clone;
    color: transparent;
    position: relative;
  }

  .date {
    background-image: linear-gradient(135deg, deeppink, dodgerblue);
    font-weight: bold;
    -webkit-background-clip: text;
    -moz-background-clip: text;
    background-clip: text;
    box-decoration-break: clone;
    -webkit-box-decoration-break: clone;
    -moz-box-decoration-break: clone;
    color: transparent;
    position: relative;
  }
</style>

<!-- SCRIPTS -->
<script  src="https://cdn.staticfile.org/jquery/3.4.1/jquery.min.js" ></script>
<script  src="https://cdn.staticfile.org/twitter-bootstrap/4.4.1/js/bootstrap.min.js" ></script>
<script  src="/js/debouncer.js" ></script>
<script  src="/js/main.js" ></script>

<!-- Plugins -->


  
    <script  src="/js/lazyload.js" ></script>
  



  <script defer src="https://cdn.staticfile.org/clipboard.js/2.0.6/clipboard.min.js" ></script>
  <script  src="/js/clipboard-use.js" ></script>







  <script  src="https://cdn.staticfile.org/tocbot/4.11.1/tocbot.min.js" ></script>
  <script>
    $(document).ready(function () {
      var boardCtn = $('#board-ctn');
      var boardTop = boardCtn.offset().top;

      tocbot.init({
        tocSelector: '#tocbot',
        contentSelector: 'article.markdown-body',
        headingSelector: 'h1,h2,h3,h4,h5,h6',
        linkClass: 'tocbot-link',
        activeLinkClass: 'tocbot-active-link',
        listClass: 'tocbot-list',
        isCollapsedClass: 'tocbot-is-collapsed',
        collapsibleClass: 'tocbot-is-collapsible',
        collapseDepth: 6,
        scrollSmooth: true,
        headingsOffset: -boardTop
      });
      if ($('.toc-list-item').length > 0) {
        $('#toc').css('visibility', 'visible');
      }
    });
  </script>



  <script  src="https://cdn.staticfile.org/typed.js/2.0.11/typed.min.js" ></script>
  <script>
    var typed = new Typed('#subtitle', {
      strings: [
        '  ',
        "深拷贝浅拷贝和赋值的原理及实现剖析&nbsp;",
      ],
      cursorChar: "✨",
      typeSpeed: 80,
      loop: false,
    });
    typed.stop();
    $(document).ready(function () {
      $(".typed-cursor").addClass("h2");
      typed.start();
    });
  </script>



  <script  src="https://cdn.staticfile.org/anchor-js/4.2.2/anchor.min.js" ></script>
  <script>
    anchors.options = {
      placement: "right",
      visible: "hover",
      
      icon: "❡"
      
    };
    var el = "h1,h2,h3,h4,h5,h6".split(",");
    var res = [];
    for (item of el) {
      res.push(".markdown-body > " + item)
    }
    anchors.add(res.join(", "))
  </script>



  <script  src="/js/local-search.js" ></script>
  <script>
    var path = "/local-search.xml";
    var inputArea = document.querySelector("#local-search-input");
    inputArea.onclick = function () {
      searchFunc(path, 'local-search-input', 'local-search-result');
      this.onclick = null
    }
  </script>



  <script  src="https://cdn.staticfile.org/fancybox/3.5.7/jquery.fancybox.min.js" ></script>
  <link  rel="stylesheet" href="https://cdn.staticfile.org/fancybox/3.5.7/jquery.fancybox.min.css" />

  <script>
    $('#post img:not(.no-zoom img, img[no-zoom]), img[zoom]').each(
      function () {
        var element = document.createElement('a');
        $(element).attr('data-fancybox', 'images');
        $(element).attr('href', $(this).attr('src'));
        $(this).wrap(element);
      }
    );
  </script>







  
  
    <script>
      !function (e, t, a) {
        function r() {
          for (var e = 0; e < s.length; e++) s[e].alpha <= 0 ? (t.body.removeChild(s[e].el), s.splice(e, 1)) : (s[e].y--, s[e].scale += .004, s[e].alpha -= .013, s[e].el.style.cssText = "left:" + s[e].x + "px;top:" + s[e].y + "px;opacity:" + s[e].alpha + ";transform:scale(" + s[e].scale + "," + s[e].scale + ") rotate(45deg);background:" + s[e].color + ";z-index:99999");
          requestAnimationFrame(r)
        }

        function n() {
          var t = "function" == typeof e.onclick && e.onclick;
          e.onclick = function (e) {
            t && t(), o(e)
          }
        }

        function o(e) {
          var a = t.createElement("div");
          a.className = "heart", s.push({
            el: a,
            x: e.clientX - 5,
            y: e.clientY - 5,
            scale: 1,
            alpha: 1,
            color: c()
          }), t.body.appendChild(a)
        }

        function i(e) {
          var a = t.createElement("style");
          a.type = "text/css";
          try {
            a.appendChild(t.createTextNode(e))
          } catch (t) {
            a.styleSheet.cssText = e
          }
          t.getElementsByTagName("head")[0].appendChild(a)
        }

        function c() {
          return "rgb(" + ~~(255 * Math.random()) + "," + ~~(255 * Math.random()) + "," + ~~(255 * Math.random()) + ")"
        }

        var s = [];
        e.requestAnimationFrame = e.requestAnimationFrame || e.webkitRequestAnimationFrame || e.mozRequestAnimationFrame || e.oRequestAnimationFrame || e.msRequestAnimationFrame || function (e) {
          setTimeout(e, 1e3 / 60)
        }, i(".heart{width: 10px;height: 10px;position: fixed;background: #f00;transform: rotate(45deg);-webkit-transform: rotate(45deg);-moz-transform: rotate(45deg);}.heart:after,.heart:before{content: '';width: inherit;height: inherit;background: inherit;border-radius: 50%;-webkit-border-radius: 50%;-moz-border-radius: 50%;position: fixed;}.heart:after{top: -5px;}.heart:before{left: -5px;}"), n(), r()
      }(window, document);
    </script>
  



  <script>(function (i, s, o, g, r, a, m) {
      i['DaoVoiceObject'] = r;
      i[r] = i[r] ||
        function () {
          (i[r].q = i[r].q || []).push(arguments);
        };
      i[r].l = 1 * new Date();
      a = s.createElement(o);
      m = s.getElementsByTagName(o)[0];
      a.async = 1;
      a.src = g;
      a.charset = 'utf-8';
      m.parentNode.insertBefore(a, m);
    })(window, document, 'script', ('https:' === document.location.protocol ? 'https:' : 'http:') + "//widget.daovoice.io/widget/2b2e276c.js", 'daovoice');
    daovoice('init', {
      app_id: "2b2e276c",
    });
    daovoice('update');
  </script>










  </div>
  
</body>

</html>
<script>

  // https://uploadbeta.com/api/pictures/random/?key=BingEverydayWallpaperPicture
  // https://source.unsplash.com/random  速度慢  有图还不错
  //https://uploadbeta.com/api/pictures/random/?key=%E6%8E%A8%E5%A5%B3%E9%83%8E 随机女图
  // https://api.jlongyan.cn/api/paper/random
  function checkWallaper() {
    fetch('https://source.unsplash.com/random', {
      method: 'GET',
      mode: 'cors',
    })
      .then(res => {
        setWallaper(res.url)
        localStorage.setItem('blogWallaper',res.url)
        return res.json()
      })
      // .then(res => {
      //   console.log(res);
      //   console.log("%c随机背景图片接口开放%c，随机壁纸api详情见博客,%c小九---!", "color:red", "", "color:orange;font-weight:bold")
      //   setWallaper(res.data.CoverImg)
      //   localStorage.setItem('blogWallaper',res.data.CoverImg)
      // })
      .catch(console.error)
  }

  if(localStorage.getItem('blogWallaper')){
    setWallaper(localStorage.getItem('blogWallaper'))
  }else{
    checkWallaper()
  }

  //设置壁纸
  function setWallaper(url){
    document.getElementById('indexBackground').style.backgroundImage = "url(" + url + ")";
  }

  let timer = null
  //  检查dom是否存在当前页面  监听 
  function watchYiyan() {
    let dom = document.getElementById('yiyan')
    if (dom) {
      fetch('https://v1.hitokoto.cn')
        .then(response => response.json())
        .then(data => {
          dom.innerText = data.hitokoto
        })
        .catch(console.error)
      if (!timer) {
        clearTimeout(timer)
      }
    } else {
      timer = setTimeout(watchYiyan, 0)
    }
  }
  //  首次执行
  watchYiyan()

  setTimeout(() => {
    clearTimeout(timer)
  }, 200);


</script>
